//
//  ViewController.swift
//  ZZJSONModel
//
//  Created by 123 on 2021/1/28.
//

import Cocoa

class ViewController: NSViewController {
    
    var language: Language = .swift
    
    func objType(_ obj: Any) -> ObjType{
        return ObjType(obj: obj, userString: isUseString.state == .on)
    }

    @IBOutlet weak var jsonTextView: NSTextView!
    @IBOutlet weak var modelTextView: NSTextView!
    @IBOutlet weak var noteTextView: NSTextView!
    
    @IBOutlet weak var rootClass: NSTextField!
    @IBOutlet weak var extensionClass: NSTextField!
    @IBOutlet weak var rootName: NSTextField!
    
    @IBOutlet weak var nullClass: NSTextField!
    
    @IBOutlet weak var isUseString: NSButton!
    @IBOutlet weak var isUseStringCell: NSButtonCell!
    
    @IBOutlet weak var isHandyJSON: NSButton!
    @IBOutlet weak var isHandyJSONCell: NSButtonCell!
    
    @IBOutlet weak var isNotNil: NSButton!
    
    @IBOutlet weak var isCodable: NSButton!
    @IBOutlet weak var isCodableCell: NSButtonCell!
    
    @IBOutlet weak var isStruct: NSButton!
    
    @IBOutlet weak var popUpBtn: NSPopUpButton!
    @IBOutlet weak var popUpBtnCell: NSPopUpButtonCell!
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        /// 禁用系统将直引号转换成弯引号
        jsonTextView.isAutomaticQuoteSubstitutionEnabled = false
        modelTextView.isAutomaticQuoteSubstitutionEnabled = false
        noteTextView.isAutomaticQuoteSubstitutionEnabled = false
        
        setNormal()
        
        self.popUpBtnCell.selectItem(withTag: language.tag)
    }

    override var representedObject: Any? {
        didSet {
            
        }
    }
    
    @IBAction func popUpValueChanged(_ sender: Any) {
        let selTag = self.popUpBtn.selectedItem?.tag ?? 1
        self.language = Language(tag: selTag)
        print("\(selTag)     \(self.language.rawValue)")
        
        self.isNotNil.isEnabled = language != .oc
        self.isHandyJSON.isEnabled = language == .swift
        self.isCodable.isEnabled = language == .swift
        self.isStruct.isEnabled = language == .swift
    }
    
    @IBAction func isHandyJsonAction(_ sender: Any) {
        if isHandyJSON.state == .on {
            isCodable.state = .off
        }
    }
    
    @IBAction func isCodableAction(_ sender: NSButton) {
        if isCodable.state == .on {
            isHandyJSON.state = .off
        }
    }
    
    @IBAction func exchangeAction(_ sender: Any) {
        if (self.jsonTextView.textStorage?.string.count ?? 0) > 0 {
            exchangeJSON()
            return
        }
        
        if !(self.noteTextView.textStorage?.string.isEmpty ?? true){
            printJsonByNote()
        }
    }
    
    @IBAction func formartJsonAction(_ sender: Any) {
        guard let json = self.jsonTextView.textStorage?.string.jsonFormatPrint() else { return  }
        self.jsonTextView.textStorage?.setAttributedString(NSAttributedString(string: json, attributes: [NSAttributedString.Key.foregroundColor: DarkColor(.black, .white)]))
    }
    
    
    /// 处理注释内容
    private func notes() -> [String: String]{
        guard let noteString = noteTextView.textStorage?.string else {
            return [:]
        }
        
        let noteArr = noteString.components(separatedBy: "\n")
        
        print(noteArr)
        
        var notes = [String: String]()
        
        var index: Int = 0
        
        /// 检查属性名是否符合规范
        func isFormartName(_ name: String) -> Bool{
            return false
        }
        
        
        func getValue(_ str: String) -> (key: String?, value: String){
            var mybeKeys = [String]()
            let spaceKeys = str.components(separatedBy: " ")
            
            for (_, v) in spaceKeys.enumerated(){
                let spaceTKeys = v.components(separatedBy: "\t")
                mybeKeys.append(contentsOf: spaceTKeys)
            }
            if mybeKeys.count > 1, let noteKey = mybeKeys.first, !noteKey.isEmpty{
                let value = str.substring(from: noteKey.count).trimmingCharacters(in: .whitespacesAndNewlines)
                if value.replacingOccurrences(of: " ", with: "").isEmpty || noteKey.contains(":"){
                    return (nil, str)
                }
                return (noteKey, value)
            }
            return (nil, str)
        }
        
        var keyStr: String?
        var valueStr: String = ""

        while index < noteArr.count {
            let str = noteArr[index]
            let value = getValue(str)
            
            if let key = value.key{
                keyStr = key
                valueStr = value.value
            } else {
                valueStr.append("\n")
                valueStr.append(value.value)
            }
            
            if let key = keyStr{
                notes[key] = valueStr
            }
            index = index + 1
        }
        
        print(notes.description.jsonFormatPrint())
        return notes
    }
    
    
    /// 按照注释输出model
    private func printJsonByNote(){
        let value = notes()
        let rootClassName = self.rootClass.attributedStringValue.string
        
        modelStrings.removeAll()
        printJson(value: value, className: rootClassName.count == 0 ? "RootClass" : rootClassName)
        
        var modelString: String = ""
        modelStrings.forEach({
            modelString.append("\n\n")
            modelString.append($0)
        })
        
        self.modelTextView.textStorage?.setAttributedString(NSAttributedString(string: modelString, attributes: [NSAttributedString.Key.foregroundColor: DarkColor(.black, .white)]))
    }
    
    /// 按照json输出model
    func exchangeJSON(){
        let text = self.jsonTextView.textStorage?.string ?? ""
        // 转成字典
        guard let data = text.data(using: .utf8) else {
            self.modelTextView.textStorage?.setAttributedString(NSAttributedString(string: "JSON 格式错误", attributes: [NSAttributedString.Key.foregroundColor: NSColor.red]))
            return
        }
        guard let value = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) else{
            self.modelTextView.textStorage?.setAttributedString(NSAttributedString(string: "JSON 转换错误", attributes: [NSAttributedString.Key.foregroundColor: NSColor.red]))
            return
        }
        let rootClassName = self.rootClass.attributedStringValue.string
        
        modelStrings.removeAll()
        printJson(value: value, className: rootClassName.count == 0 ? "RootClass" : rootClassName)
        
        var modelString: String = ""
        modelStrings.forEach({
            modelString.append("\n\n")
            modelString.append($0)
        })
        
        self.modelTextView.textStorage?.setAttributedString(NSAttributedString(string: modelString, attributes: [NSAttributedString.Key.foregroundColor: DarkColor(.black, .white)]))
    }
    
    var modelStrings: [String] = []
    
    private func className(_ key: String) -> String{
        let first = key.firstString.capitalized;
        let className = "\(first)\(key.substring(from:1))"
//        print("class name is \(className)")
        return className
    }
    
    
    func printJson(value: Any, className: String){
        switch self.language {
            case .swift:
                printSwiftJson(value: value, className: className)
            case .oc:
                printJsonWithObjc(value: value, className: className)
            case .dart:
                printDartJson(value: value, className: className)
        }
    }
}

func DarkColor(_ light: NSColor,_ dark: NSColor) -> NSColor{
   return NSColor(name: nil) { (appearance) -> NSColor in
        if appearance.bestMatch(from: [NSAppearance.Name.darkAqua,NSAppearance.Name.aqua]) == NSAppearance.Name.darkAqua{
            return dark
        }
        return light
    }
}

// MARK: - OC
extension ViewController{
    func printJsonWithObjc(value: Any, className: String){
        var extensionClassName = self.extensionClass.attributedStringValue.string
        
        if extensionClassName.count > 0 {
            extensionClassName = ": \(extensionClassName)"
        }else{
            extensionClassName = ": NSObject"
        }
        
        let notes = notes()
        
        if value is [String: Any] {
            let classNameStr = "\(self.rootName.attributedStringValue.string)\(className)"
            var modelString: String = "\n\n🌟🌟🌟 \(classNameStr).h文件 🌟🌟🌟 👇👇👇"
            
            let head = """
                        #import <Foundation/Foundation.h>
                        
                        NS_ASSUME_NONNULL_BEGIN
                        """
            modelString.append("\n\n\(head)")
            modelString.append("\n\n@interface \(classNameStr)\(extensionClassName)\n")
            
            
            let dic = (value as? [String: Any]) ?? [:]
            
            let keys = dic.keys
            let newKeys = keys.sorted()
            for (_, key) in newKeys.enumerated() {
                guard let value = dic[key] else{
                    continue
                }
                
                if let note = notes[key] {
                    let notes = note.components(separatedBy: "\n")
                    for (_ , v) in notes.enumerated(){
                        if !v.isEmpty{
                            modelString.append("\n\(language == .oc ? "" : "\t")/// \(v)")
                        }
                    }
                }
                
                let type = objType(value)
                switch type {
                    case .int, .double, .float, .bool:
                        modelString.append("\n@property (assign, nonatomic) \(type.unit(with: self.language)) \(key);")
                    case .string:
                        modelString.append("\n@property (copy, nonatomic) \(type.unit(with: self.language)) *\(key);")
                    case .dic:
                        let className = self.className(key)
                        let valueName = "\(self.rootName.attributedStringValue.string)\(className)"
                        modelString.append("\n@property (strong, nonatomic) \(valueName) *\(key);")
                        printJsonWithObjc(value: value, className: className)
                        break
                    case .array:
                        let className = self.className(key)
                        let valueName = "\(self.rootName.attributedStringValue.string)\(className)"
                        //                        if isNotNil.state == .on {
                        //                            modelString.append("\n\tvar \(key): [\(valueName)] = []")
                        //                        }else{
                        //                            modelString.append("\n\tvar \(key): [\(valueName)]?")
                        //                        }
                        modelString.append("\n@property (strong, nonatomic) NSArray<\(valueName) *> *\(key);")
                        guard let arr = value as? [Any], let obj = arr.first else {
                            printJsonWithObjc(value: (Any).self, className: className)
                            break
                        }
                        printJsonWithObjc(value: obj, className: className)
                        break
                    case .null:
                        let nullClassName = self.nullClass.attributedStringValue.string
                        guard nullClassName.count > 0 else {
                            modelString.append("\n@property (strong, nonatomic) \(type.unit(with: self.language)) *\(key);")
                            break
                        }
                        let valueName = "\(nullClassName)"
                        modelString.append("\n@property (strong, nonatomic) \(valueName) *\(key);")
                        printJsonWithObjc(value: value, className: className)
                        break
                    default:
                        //                        modelString.append("\n@property (strong, nonatomic) \(type.ocUnit) \(key);")
                        modelString.append("\n@property (strong, nonatomic) \(type.unit(with: self.language)) *\(key);")
                        break
                }
            }
            
            modelString.append("\n\n@end")
            modelString.append("\n\nNS_ASSUME_NONNULL_END")
            
            let endStr = """
                        #import "\(classNameStr).h"
                        
                        @implementation \(classNameStr)
                        
                        @end
                        
                        """
            
            modelString.append("\n\n🌟🌟🌟 \(classNameStr).m文件 🌟🌟🌟 👇👇👇")
            modelString.append("\n\n\(endStr)")
            
            modelStrings.append(modelString)
            return
        }
        
        if value is [Any] {
            guard let arr = value as? [Any], let obj = arr.first else {
                return
            }
            printJson(value: obj, className: className)
            return
        }
    }
    
}

// MARK: - Swift
extension ViewController{
    
    func printSwiftJson(value: Any, className: String){
        
        var extensionClassName = self.extensionClass.attributedStringValue.string
        
        // struct 不能继承至NSObject
        if isStruct.state == .on && extensionClassName == "NSObject"{
            extensionClassName = ""
        }
        
        if extensionClassName.count > 0 {
            extensionClassName = ": \(extensionClassName)"
            if isHandyJSON.state == .on {
                extensionClassName.append(", HandyJSON")
            }
            
            if isCodable.state == .on{
                extensionClassName.append(", Codable")
            }
        }else{
            if isHandyJSON.state == .on {
                extensionClassName = ": HandyJSON"
            }
            
            if isCodable.state == .on{
                extensionClassName.append(": Codable")
            }
        }
        
        let notes = notes()
        
        if value is [String: Any] {
            var modelString: String = "class \(self.rootName.attributedStringValue.string)\(className)\(extensionClassName) {"
            
            if isStruct.state == .on{
                modelString = modelString.replacingOccurrences(of: "class", with: "struct")
            }
            
            let dic = (value as? [String: Any]) ?? [:]
            
            let keys = dic.keys
            let newKeys = keys.sorted()
            for (_, key) in newKeys.enumerated() {
                guard let value = dic[key] else{
                    continue
                }
                
                if let note = notes[key] {
                    let notes = note.components(separatedBy: "\n")
                    for (_ , v) in notes.enumerated(){
                        if !v.isEmpty{
                            modelString.append("\n\t/// \(v)")
                        }
                    }
                }
                
                let type = objType(value)
                switch type {
                    case .string, .int, .double, .float, .bool:
                        if isNotNil.state == .on {
                            modelString.append("\n\tvar \(key): \(type.unit(with: self.language))")
                            if isCodable.state == .off{
                                modelString.append(" = \(type.normalValue)")
                            }
                        }else{
                            modelString.append("\n\tvar \(key): \(type.unit(with: self.language))?")
                        }
                    case .dic:
                        let className = self.className(key)
                        let valueName = "\(self.rootName.attributedStringValue.string)\(className)"
                        if isNotNil.state.rawValue == 1 {
                            modelString.append("\n\tvar \(key): \(valueName) = \(valueName)()")
                        }else{
                            modelString.append("\n\tvar \(key): \(valueName)?")
                        }
                        printJson(value: value, className: className)
                        break
                    case .array:
                        let className = self.className(key)
                        let valueName = "\(self.rootName.attributedStringValue.string)\(className)"
                        if isNotNil.state == .on {
                            modelString.append("\n\tvar \(key): [\(valueName)] = []")
                        }else{
                            modelString.append("\n\tvar \(key): [\(valueName)]?")
                        }
                        guard let arr = value as? [Any], let obj = arr.first else {
                            printJson(value: (Any).self, className: className)
                            break
                        }
                        printJson(value: obj, className: className)
                        break
                    case .null:
                        let nullClassName = self.nullClass.attributedStringValue.string
                        guard nullClassName.count > 0 else {
                            modelString.append("\n\tvar \(key): Any?")
                            break
                        }
                        let valueName = "\(nullClassName)"
                        modelString.append("\n\tvar \(key): \(valueName)?")
                        printJson(value: value, className: className)
                        break
                    default:
                        if isNotNil.state.rawValue == 1 {
                            modelString.append("\n\tvar \(key): Any!")
                        }else{
                            modelString.append("\n\tvar \(key): Any?")
                        }
                        break
                }
            }
            if isHandyJSON.state == .on && isStruct.state == .off {
                let extensionClassName = self.extensionClass.attributedStringValue.string
                if extensionClassName.contains("NSObject") {
                    modelString.append("\n\n\trequired override init() { }")
                }else{
                    modelString.append("\n\n\trequired init() { }")
                }
            }
            modelString.append("\n}")
            modelStrings.append(modelString)
            return
        }
        
        if value is [Any] {
            guard let arr = value as? [Any], let obj = arr.first else {
                return
            }
            printJson(value: obj, className: className)
            return
        }
    }
}

// MARK: - Dart
extension ViewController{
    func printDartJson(value: Any, className: String){
        
        var extensionClassName = self.extensionClass.attributedStringValue.string
        
        if extensionClassName.count > 0 {
            extensionClassName = ": \(extensionClassName)"
        }
        
        let notes = notes()
        
        if value is [String: Any] {
            var modelString: String = "class \(self.rootName.attributedStringValue.string)\(className)\(extensionClassName) {"
            
            let dic = (value as? [String: Any]) ?? [:]
            
            let keys = dic.keys
            let newKeys = keys.sorted()
            for (_, key) in newKeys.enumerated() {
                guard let value = dic[key] else{
                    continue
                }
                
                if let note = notes[key] {
                    let notes = note.components(separatedBy: "\n")
                    for (_ , v) in notes.enumerated(){
                        if !v.isEmpty{
                            modelString.append("\n\t/// \(v)")
                        }
                    }
                }
                
                let type = objType(value)
                switch type {
                    case .string, .int, .double, .float, .bool:
                        if isNotNil.state == .on {
//                            modelString.append("\n\tvar \(key): \(type.unit(with: self.language))")
                            modelString.append("\n\t\(type.unit(with: self.language)) \(key);")
                        }else{
                            modelString.append("\n\t\(type.unit(with: self.language))? \(key);")
                        }
                    case .dic:
                        let className = self.className(key)
                        let valueName = "\(self.rootName.attributedStringValue.string)\(className)"
                        if isNotNil.state.rawValue == 1 {
                            modelString.append("\n\t\(valueName) \(key);")
                        }else{
                            modelString.append("\n\t\(valueName)? \(key);")
                        }
                        printJson(value: value, className: className)
                        break
                    case .array:
                        let className = self.className(key)
                        let valueName = "\(self.rootName.attributedStringValue.string)\(className)"
                        if isNotNil.state == .on {
                            modelString.append("\n\tList<\(valueName)> \(key);")
                        }else{
                            modelString.append("\n\tList<\(valueName)>? \(key);")
                        }
                        guard let arr = value as? [Any], let obj = arr.first else {
                            printJson(value: (Any).self, className: className)
                            break
                        }
                        printJson(value: obj, className: className)
                        break
                    case .null:
                        let nullClassName = self.nullClass.attributedStringValue.string
                        guard nullClassName.count > 0 else {
                            modelString.append("\n\tdynamic \(key);")
                            break
                        }
                        let valueName = "\(nullClassName)"
                        modelString.append("\n\t\(valueName)? \(key);")
                        printJson(value: value, className: className)
                        break
                    default:
                        modelString.append("\n\tdynamic \(key);")
                        break
                }
            }
            modelString.append("\n}")
            modelStrings.append(modelString)
            return
        }
        
        if value is [Any] {
            guard let arr = value as? [Any], let obj = arr.first else {
                return
            }
            printJson(value: obj, className: className)
            return
        }
    }
    
}

fileprivate extension ViewController{
    
    static let normalJson = """
                            
                            {
                                "name": "张三",
                                "age": 18,
                                "age2": 18.11,
                                "age3": 199999999999999998.11,
                                "sex": "woman",
                                "sex2": "null",
                                "sex3": "Null",
                                "sex4": "NULL",
                                "sex5": null,
                            }

                            """
    
    static let normalJsonNote = """
                            name 姓名
                            age 年龄
                            sex 性别：String类型 woman:女 man:男
                            """
    func setNormal() {
        
        
        self.jsonTextView.textStorage?
            .setAttributedString(NSAttributedString(string: ViewController.normalJson,
                                                    attributes: [NSAttributedString.Key.foregroundColor: NSColor.white]))
        
        self.noteTextView.textStorage?
            .setAttributedString(NSAttributedString(string: ViewController.normalJsonNote,
                                                    attributes: [NSAttributedString.Key.foregroundColor: NSColor.white]))
    }
}
